﻿using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using DXFLib;
using Graph3D.Vrml;
using Graph3D.Vrml.Fields;
using Graph3D.Vrml.Nodes;
using Graph3D.Vrml.Nodes.Geometry;
using Graph3D.Vrml.Nodes.Grouping;
using Graph3D.Vrml.Parser;
using Graph3D.Vrml.Tokenizer;
using LayoutPlanning.Views;
using Brush = System.Windows.Media.Brush;
using Path = System.Windows.Shapes.Path;
using Rectangle = System.Windows.Shapes.Rectangle;

namespace LayoutPlanning.Model
{
    public class DFXImporter
    {
        private static double scaleX = 1;
        private static double scaleY = 1;


        public static List<Shape> LoadVrml(string filename)
        {
            var sc =   LoadScene(filename);
            var shapes =  GetShapes(sc.root);
            double minLeft = shapes.OfType<Line>().Min(i => Math.Min(i.X1, i.X2));
            double maxLeft = shapes.OfType<Line>().Max(i => Math.Max(i.X1, i.X2));
            double minTop = shapes.OfType<Line>().Min(i => Math.Min(i.Y1, i.Y2));
            double maxTop = shapes.OfType<Line>().Max(i => Math.Max(i.Y1, i.Y2));
            var scale = 1d;
            if (Math.Min(maxTop - minTop, maxLeft - minLeft) < 1000)
            {
                var dialog = new ChooseScale();
                if (dialog.ShowDialog() ?? false)
                {
                    scale = dialog.Scale;
                }
            }
            OffsetLines(shapes, scale, System.Windows.Media.Brushes.SteelBlue);
            return shapes;
        }

        private static List<Shape> GetShapes(GroupingNode sc)
        {
            var list = new List<Shape>();
            foreach (var baseNode in sc.children.Values)
            { 
                if(baseNode is GroupingNode)
                    list.AddRange(GetShapes((GroupingNode) baseNode));
                try 
                {
                    if (baseNode is ShapeNode)
                    {
                        var geometry = (ShapeNode) baseNode;

                        if (geometry.geometry.Node is IndexedFaceSetNode)
                        {
                            var inFSN = geometry.geometry.Node as IndexedFaceSetNode;
                            var coord = inFSN.coord.Node as CoordinateNode;
                            for (int i = 1; i < coord.point.length  ; i++)
                            {
                                list.Add(new Line() {X1 = coord.point[i-1].x, Y1 = coord.point[i-1].y, X2= coord.point[i].x, Y2 = coord.point[i].y});
                            }
                        }
                        if (geometry.geometry.Node is BoxNode)
                        {
                            var inFSN = geometry.geometry.Node as BoxNode;
                            var t = inFSN.size;

                            list.Add(new Rectangle() { Width = t.x, Height = t.y });
                        }
                        
                    }
                }
                catch (Exception)

                {
                    ;
                }
            }
            return list;
        }

        private static VrmlScene LoadScene(string name)
        {
            using (var stream = File.OpenRead(name))
            {
                var tokenizer = new Vrml97Tokenizer(stream);
                var parser = new VrmlParser(tokenizer);
                var scene = new VrmlScene();
                parser.Parse(scene);
                return scene;
            }
        }


        public static IList<Shape> LoadDxf(string filename, double scale)
        {
            scaleX = scale;
            scaleY = scale;
            // Process open file dialog box results 
            if (!string.IsNullOrEmpty(filename))
            {
                // Parse DXF
                DateTime start = DateTime.UtcNow;

                DXFDocument doc = new DXFDocument();

                doc.Load(filename);

                System.Diagnostics.Debug.WriteLine("Loaded {0} in {1}ms", System.IO.Path.GetFileName(filename),
                    DateTime.UtcNow.Subtract(start).TotalMilliseconds);

                // Process entities
                if (doc.Entities.Count > 0)
                {
                    // Create shapes
                    start = DateTime.UtcNow;

                    IList<Shape> shapes = new List<Shape>();
                    foreach (DXFEntity entity in doc.Entities)
                        ConvertDxfEntityToShapes(doc, entity, shapes, System.Windows.Media.Brushes.SteelBlue);

                    System.Diagnostics.Debug.WriteLine("Created shapes in {0}ms",
                        DateTime.UtcNow.Subtract(start).TotalMilliseconds);
                    OffsetLines(shapes, 1, System.Windows.Media.Brushes.SteelBlue);
                    //var can = new Canvas();
                    //foreach (var shape in shapes)
                    //{
                    //    can.Children.Add(shape);
                    //}
                    //double minLeft = can.Children.OfType<Line>().Min(i => Math.Min(i.X1, i.X2));
                    //double maxLeft = can.Children.OfType<Line>().Max(i => Math.Max(i.X1, i.X2));
                    //double minTop = can.Children.OfType<Line>().Min(i => Math.Min(i.Y1, i.Y2));
                    //double maxTop = can.Children.OfType<Line>().Max(i => Math.Max(i.Y1, i.Y2));
                    //can.UpdateLayout();
                    return shapes;
                }
            }

            return null;
        }

        private static void OffsetLines(IList<Shape> shapes, double scale, Brush brush)
        {
            double minLeft = shapes.OfType<Line>().Min(i => Math.Min(i.X1, i.X2));
            double maxLeft = shapes.OfType<Line>().Max(i => Math.Max(i.X1, i.X2));
            double minTop = shapes.OfType<Line>().Min(i => Math.Min(i.Y1, i.Y2));
            double maxTop = shapes.OfType<Line>().Max(i => Math.Max(i.Y1, i.Y2));
            double strokeThickness = Math.Min(Math.Max(maxTop - minTop, maxLeft-minLeft)*0.01d,1d);


            if (minLeft != 0f || maxLeft != 0f)
            {
                //Move objects
                foreach (Shape shape in shapes.OfType<Line>())
                {
                    var line = shape as Line;
                    if (line != null)
                    {
                        line.X1 -= minLeft;
                        line.X2 -= minLeft;
                        line.Y1 -= minTop;
                        line.Y2 -= minTop;
                        line.X1 *= scale;
                        line.X2 *= scale;
                        line.Y1 *= scale;
                        line.Y2 *= scale;
                    }
                    shape.Stroke = brush;
                    shape.StrokeThickness = strokeThickness;
                }
            }
        }

        private static void ConvertDxfEntityToShapes(DXFDocument doc, DXFEntity entity, IList<Shape> shapes,
            System.Windows.Media.Brush stroke)
        {
            if (entity is DXFLine)
            {
                DXFLine line = (DXFLine) entity;
                PointF start = new PointF((float) line.Start.X, (float) -line.Start.Y);
                PointF end = new PointF((float) line.End.X, (float) -line.End.Y);

                Line drawLine = new Line();
                drawLine.Stroke = stroke;
                drawLine.X1 = end.X*scaleX;
                drawLine.X2 = start.X*scaleX;
                drawLine.Y1 = end.Y*scaleY;
                drawLine.Y2 = start.Y*scaleY;
                drawLine.StrokeThickness = 1;
                drawLine.IsHitTestVisible = false;

                shapes.Add(drawLine);
            }
            else if (entity is DXFCircle)
            {
                DXFCircle circle = (DXFCircle) entity;
                Ellipse drawCircle = new Ellipse();
                drawCircle.StrokeThickness = 1;
                drawCircle.Stroke = stroke;
                drawCircle.Width = circle.Radius*2*scaleX;
                drawCircle.Height = circle.Radius*2*scaleY;
                drawCircle.IsHitTestVisible = false;
                Canvas.SetLeft(drawCircle, (circle.Center.X.Value - circle.Radius)*scaleX);
                Canvas.SetTop(drawCircle, (-circle.Center.Y.Value - circle.Radius)*scaleY);

                shapes.Add(drawCircle);
            }
            else if (entity is DXFArc)
            {
                DXFArc arc = (DXFArc) entity;

                Path path = new Path();
                path.Stroke = stroke;
                path.StrokeThickness = 1;

                System.Windows.Point endPoint = new System.Windows.Point(
                    (arc.Center.X.Value + Math.Cos(arc.EndAngle*Math.PI/180)*arc.Radius)*scaleX,
                    (-arc.Center.Y.Value - Math.Sin(arc.EndAngle*Math.PI/180)*arc.Radius)*scaleY);

                System.Windows.Point startPoint = new System.Windows.Point(
                    (arc.Center.X.Value + Math.Cos(arc.StartAngle*Math.PI/180)*arc.Radius)*scaleX,
                    (-arc.Center.Y.Value - Math.Sin(arc.StartAngle*Math.PI/180)*arc.Radius)*scaleY);

                ArcSegment arcSegment = new ArcSegment();
                double sweep = 0.0;
                if (arc.EndAngle < arc.StartAngle)
                    sweep = (360 + arc.EndAngle) - arc.StartAngle;
                else sweep = Math.Abs(arc.EndAngle - arc.StartAngle);

                arcSegment.IsLargeArc = sweep >= 180;
                arcSegment.Point = endPoint;
                arcSegment.Size = new System.Windows.Size(arc.Radius*scaleX, arc.Radius*scaleY);
                arcSegment.SweepDirection = arc.ExtrusionDirection.Z >= 0
                    ? SweepDirection.Counterclockwise
                    : SweepDirection.Clockwise;

                PathGeometry geometry = new PathGeometry();
                PathFigure pathFigure = new PathFigure();
                pathFigure.StartPoint = startPoint;
                pathFigure.Segments.Add(arcSegment);
                geometry.Figures.Add(pathFigure);

                path.Data = geometry;
                path.IsHitTestVisible = false;
                shapes.Add(path);
            }
            else if (entity is DXFPolyLine)
            {
                DXFPolyLine polyLine = (DXFPolyLine) entity;
                bool isClosed = polyLine.Flags == DXFPolyLine.FlagsEnum.closed;

                int count = isClosed ? polyLine.Children.Count : polyLine.Children.Count - 1;
                for (int i = 1; i <= count; i++)
                {
                    DXFVertex vertex1 = (i == polyLine.Children.Count)
                        ? (DXFVertex) polyLine.Children[0]
                        : (DXFVertex) polyLine.Children[i];
                    DXFVertex vertex2 = (DXFVertex) polyLine.Children[i - 1];

                    PointF start = new PointF((float) vertex1.Location.X, (float) -vertex1.Location.Y);
                    PointF end = new PointF((float) vertex2.Location.X, (float) -vertex2.Location.Y);

                    // TODO: Handle Vertex.Buldge http://www.afralisp.net/archive/lisp/Bulges1.htm

                    Line drawLine = new Line();
                    drawLine.Stroke = stroke;
                    drawLine.X1 = end.X*scaleX;
                    drawLine.X2 = start.X*scaleX;
                    drawLine.Y1 = end.Y*scaleY;
                    drawLine.Y2 = start.Y*scaleY;
                    drawLine.StrokeThickness = 1;
                    drawLine.IsHitTestVisible = false;
                    shapes.Add(drawLine);
                }
            }
            else if (entity is DXFLWPolyLine)
            {
                DXFLWPolyLine polyLine = (DXFLWPolyLine) entity;
                bool isClosed = polyLine.Flags == DXFLWPolyLine.FlagsEnum.closed;

                int count = isClosed ? polyLine.Elements.Count : polyLine.Elements.Count - 1;
                for (int i = 1; i <= count; i++)
                {
                    DXFPoint vertex1 = (i == polyLine.Elements.Count)
                        ? polyLine.Elements[0].Vertex
                        : polyLine.Elements[i].Vertex;
                    DXFPoint vertex2 = polyLine.Elements[i - 1].Vertex;

                    // TODO: Handle Element.Bulge http://www.afralisp.net/archive/lisp/Bulges1.htm

                    PointF start = new PointF((float) vertex1.X, (float) -vertex1.Y);
                    PointF end = new PointF((float) vertex2.X, (float) -vertex2.Y);

                    Line drawLine = new Line();
                    drawLine.Stroke = stroke;
                    drawLine.X1 = end.X*scaleX;
                    drawLine.X2 = start.X*scaleX;
                    drawLine.Y1 = end.Y*scaleY;
                    drawLine.Y2 = start.Y*scaleY;
                    drawLine.StrokeThickness = 1;
                    drawLine.IsHitTestVisible = false;

                    shapes.Add(drawLine);
                }
            }
            else if (entity is DXFMText)
            {
            }
            else if (entity is DXFHatch)
            {
            }
            else if (entity is DXF3DFace)
            {
            }
            else if (entity is DXFInsert)
            {
                return;
            }
            else if (entity is DXFSolid)
            {
            }
            else if (entity is DXFText)
            {
                DXFText dxfText = (DXFText) entity;
            }
            else if (entity is DXFTrace)
            {
            }
            else if (entity is DXFSpline)
            {
            }
            else if (entity is DXFPointEntity)
            {
            }
            else if (entity is DXFXLine)
            {
            }
            else if (entity is DXFViewPort)
            {
            }
            else
            {
                //                    
            }
        }
    }
}